module nade.ast;

import std.string;
import std.conv;


class AST {
	this(string label, AST[] ast ...)
	{
		_label = label;
		_children = ast.dup;
	}
	this(ref string[] tokens)
	{
		string take() { string token = tokens[0]; tokens = tokens[1 .. $]; return token; }

		_label = take();
		assert(_label.length > 0);
		assert(_label != "(");
		assert(_label != ")");

		while(tokens.length > 0)
		{
			string token = take();

			if(token == "(")
			{
				_children ~= new AST(tokens);
			}
			else if(token == ")")
			{
				break;
			}
			else
			{
				_children ~= new AST(token);
			}
		}
	}

	AST dup()
	{
		AST[] children;
		children.length = _children.length;
		foreach(k, child; _children)
		{
			children[k] = child.dup;
		}

		return new AST(_label, children);
	}

	T as(T)() { return to!(T)(label); }

	T get(T)(string path, T def = T.init)
	{
		AST node = opIndex(path);
		if(node is null) return def;
		return node.as!(T);
	}

	AST opIndex(uint index) { return _children[index]; }
	AST opIndex(string path)
	{
		assert(path.length > 0);

		string r = path;
		string t = r.munch("^.");

		if(t[0] == '#')
		{
			const i = to!(uint)(t[1 .. $]);
			if(i >= _children.length) return null;
			auto child = _children[i];

			if(r.length > 0)
				return child[r[1 .. $]];
			else
				return child;
		}
		else
		{
			foreach(child; _children)
			{
				if(child.label == t)
				{
					if(r.length > 0)
						return child[r[1 .. $]];
					else
						return child;
				}
			}
		}

		return null;
	}
	AST opSlice(uint from, uint to)
	{
		return new AST(_label, _children[from .. to]);
	}

	AST opCatAssign(AST rhs) { _children ~= rhs; return this; }

	int opApply(int delegate(ref AST) dg)
	{
		foreach(child; _children)
		{
			const r = dg(child);
			if(r != 0) return r;
		}

		return 0;
	}
	int opApplyReverse(int delegate(ref AST) dg)
	{
		foreach_reverse(child; _children)
		{
			const r = dg(child);
			if(r != 0) return r;
		}

		return 0;
	}
	int opApply(int delegate(ref uint, ref AST) dg)
	{
		foreach(k, child; _children)
		{
			const r = dg(k, child);
			if(r != 0) return r;
		}

		return 0;
	}
	int opApplyReverse(int delegate(ref uint, ref AST) dg)
	{
		foreach_reverse(k, child; _children)
		{
			const r = dg(k, child);
			if(r != 0) return r;
		}

		return 0;
	}

	override string toString()
	{
		if(_children.length == 0)
		{
			assert(_label.length > 0);
			if(countchars(_label, "() \t\n\r" ~ '"') > 0) return '"' ~ _label ~ '"';
			return _label;
		}

		string r = "(" ~ _label;
		foreach(child; _children)
		{
			r ~= " " ~ child.toString;
		}

		return r ~ ")";
	}

	uint length() { return _children.length; }

	string label() { return _label; }
	string label(string rhs) { return _label = rhs; }

	private {
		string		_label;
		AST[]		_children;
	}
	static {
		AST parse(string text)
		{
			string[] tokens;
			string buff;

			char take() { char c = text[0]; text = text[1 .. $]; return c; }
			void give(char c) { text = c ~ text; }
			void token() { if(buff.length > 0) tokens ~= to!(string)(buff.dup), buff = ""; }

			while(text.length > 0)
			{
				char c = take();

				switch(c)
				{
					case ' ':
					case '\t':
					case '\r':
					case '\n':
					{
						token();
						break;
					}

					case '(':
					{
						token();
						tokens ~= "(";
						break;
					}

					case ')':
					{
						token();
						tokens ~= ")";
						break;
					}

					case '"':
					{
						token();
						c = take();
						while(c != '"')
						{
							buff ~= c;
							c = take();

							if(c == '\\')
							{
								c = take();
								if(c >= 'a'  &&  c <= 'z')
								{
									c -= 'a' - 1;
								}
							}
						}
						tokens ~= buff;
						buff = "";
						break;
					}

					case '{':
					{
						token();
						c = take();
						while(c != '}')
						{
							c = take();
						}
						buff = "";
						break;
					}

					default:
					{
						buff ~= c;
					}
				}
			}

			token();

			return new AST(tokens);
		}
	}
}
